import os
import copy
import cPickle

from utils.persist import Persistent
from utils.namespace import Namespace
from utils.interval import Interval

from cranesched.instance.precedences import PrecedenceGraph


class Instance(Persistent):
    """An instance of the Crane Scheduling problem. This is composed of a set of activities and 
    their precedence relationships (called the precedence graph), and the initial state of a 
    warehouse. The initial time and state of the cranes (including ongoing assignments) must also 
    be known."""
    def __init__(self, prec_graph, warehouse, **meta):
        if not isinstance(prec_graph, PrecedenceGraph):
            prec_graph = PrecedenceGraph(prec_graph)
        self.prec_graph = prec_graph               # activity precedence graph
        self.warehouse = copy.deepcopy(warehouse)  # initial warehouse state
        self.meta = Namespace(meta)                # instance metadata namespace
        
    def preprocess(self, verbose=False):
        """Pre-processes the precedence graph and computes missing warehouse limits."""
        self.prec_graph.preprocess(verbose)
        self.determine_warehouse_limits()
        
    def determine_warehouse_limits(self, force_x=False, force_y=False):
        """This method can be used to automatically determine warehouse limits in order to 
        accomodate for all places and cranes, such that it is guaranteed that all cranes are able 
        to reach all places inside the warehouse. Use this method when the actual dimensions of 
        the warehouse are unknown."""
        warehouse = self.warehouse
        if force_x or warehouse.xlimits is None:
            total_crane_width = sum(crane.x.size for crane in self.warehouse.cranes)
            Xs = ([activity.x for activity in self.prec_graph] + 
                  [crane.x.position for crane in self.warehouse.cranes])
            warehouse.xlimits = Interval(min(Xs) - total_crane_width, 
                                         max(Xs) + total_crane_width)
        if force_y or warehouse.ylimits is None:
            total_crane_length = sum(crane.y.size for crane in self.warehouse.cranes)
            Ys = ([activity.y for activity in self.prec_graph] + 
                  [crane.y.position for crane in self.warehouse.cranes])
            warehouse.ylimits = Interval(min(Ys) - total_crane_length,
                                         max(Ys) + total_crane_length)
            
    def show_cranes(self):
        """Auxiliary method for debugging purposes."""
        from cranesched.routing import Router
        
        router = Router(*self.warehouse.xlimits)
        for crane in self.warehouse.cranes:
            crane.deepcopy().set_router(router)
        router.update()
        router.show()
        return router
        
    # --------------------------------------------------------------------------
    # Persistence protocol
    pickle_module = cPickle
    pickle_protocol = 2
    
    def __presave__(self, filepath, **kwargs):
        if filepath is None and "filepath" not in self.meta:
                raise Exception("output file path required")
        elif filepath is not None:
            filepath = os.path.abspath(filepath)
            directory, filename = os.path.split(filepath)
            self.meta.filepath = filepath
            self.meta.filename = filename
            self.meta.directory = directory
        self.prec_graph.unlink()
        kwargs.setdefault("pickle", type(self).pickle_module)
        kwargs.setdefault("protocol", type(self).pickle_protocol)
        return (self.meta.filepath,), kwargs
        
    def __postsave__(self, filepath, **kwargs):
        self.prec_graph.link()
        
    @classmethod
    def __preload__(cls, filepath, **kwargs):
        kwargs.setdefault("pickle", cls.pickle_module)
        kwargs.setdefault("protocol", cls.pickle_protocol)
        return (filepath,), kwargs
        
    def __postload__(self, filepath, **kwargs):
        self.prec_graph.link()
        
        
